import traceback
from enum import Enum

from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi import status
from fastapi.responses import JSONResponse

from utils.logger import Logger
from utils.response import json_response, template


class AppErrorCode(Enum):
    systemError = 1000


ErrorMessages = {
    AppErrorCode.systemError: '系统错误'
}


class AppException(Exception):
    def __init__(self, error_code=AppErrorCode.systemError, error_message='', error_detail='', error_data=None):
        self.error_code = error_code
        self.error_message = ErrorMessages[error_code]
        if error_message:
            self.error_message += ' - Message: ' + error_message
        self.error_detail = error_detail if error_detail else traceback.format_exc()
        self.error_data = error_data if error_data else {}

    def __str__(self):
        error_str = f'[Error] {self.error_code.value} - {self.error_code.name}. Message: {self.error_message}.'
        if self.error_data:
            error_str += f' Data: {self.error_data}'
        return error_str

    def __repr__(self):
        return self.__str__()


def fastapi_exception_handler(app):
    """fastapi异常拦截处理"""

    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(_, exc):
        """
        参数错误日志打印
        """
        Logger.error('RequestValidationError: ' + str({'detail': exc.errors(), 'body': exc.body}))
        return JSONResponse(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            content=jsonable_encoder({'detail': exc.errors()}),
        )

    @app.exception_handler(404)
    async def not_found_exception_handler(request, _):
        route_type = 'web'
        if request.url.path.startswith('/api'):
            route_type = 'api'

        if route_type == 'web':
            return template(
                request=request,
                status_code=status.HTTP_404_NOT_FOUND,
                name='404_500.html',
                context={
                    'code': '404',
                    'message': 'Page Not Found',
                    'debug': False,
                }
            )
        else:
            return json_response(
                request_id=request.state.request_id,
                status_code=status.HTTP_404_NOT_FOUND,
                message='Page Not Found',
                data={
                    'debug': request.state.debug,
                }
            )

    @app.exception_handler(500)
    @app.exception_handler(AppException)
    async def not_found_exception_handler(request, exc):
        request.state.logger.exception(exc)

        if not isinstance(exc, AppException):
            exc = AppException(
                error_code=AppErrorCode.systemError,
                error_message=str(exc),
                error_detail=traceback.format_exc()
            )

        route_type = 'web'
        if request.url.path.startswith('/api'):
            route_type = 'api'

        error_data = {}
        if request.state.debug:
            error_data = {
                'error_code': exc.error_code.value,
                'error_message': exc.error_message,
                'error_detail': exc.error_detail,
                'error_data': exc.error_data
            }

        if route_type == 'web':
            return template(
                request=request,
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                name='404_500.html',
                context={
                    'code': status.HTTP_500_INTERNAL_SERVER_ERROR,
                    'message': 'Internal Server Error',
                    'debug': request.state.debug,
                    **error_data
                }
            )
        else:
            return json_response(
                request_id=request.state.request_id,
                status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
                message='Internal Server Error',
                data={
                    'debug': request.state.debug,
                    **error_data
                }
            )
